﻿#include "pch.h"
#include "qgp/qgach.h"
#include "qg_blits.h"

//////////////////////////////////////////////////////////////////////////
// 이미지
KIMPL_SUBCLASS(qgImage, "Image");

//
qgImage::qgImage(kint width, kint height, qgColorFormat fmt)
{
	Initialize(width, height, fmt);
}

//
qgImage::qgImage(kint width, kint height, qgColorFormat fmt, kpointer data, kint size, bool alloc /*= true*/)
{
	Initialize(width, height, fmt, data, size, alloc);
}

//
qgImage::~qgImage()
{
	Dispose();
}

//
void qgImage::Initialize(kint width, kint height, qgColorFormat fmt)
{
	_prop = qgAchSupp::ToPixelProp(fmt);
	_pitch = _prop.bpp * width;
	_width = width;
	_height = height;

	_alloc = true;
	_size = _pitch * height;
	_data = k_new(_size, kbyte);
}

//
void qgImage::Initialize(kint width, kint height, qgColorFormat fmt, kpointer data, kint size, bool rem /*= true*/)
{
	_prop = qgAchSupp::ToPixelProp(fmt);
	_pitch = _prop.bpp * width;
	_width = width;
	_height = height;

	_alloc = rem;
	_size = size;
	_data = (kbyte*)data;
}

//
void qgImage::Dispose()
{
	if (_alloc && _data)
	{
		k_delete(_data);
		_data = NULL;
	}
}

//
void qgImage::SetPixel(kint x, kint y, const tpl::ColorValue& cc, bool testblend /* = false */)
{
	k_return_if_fail(x >= 0 && x < _width);
	k_return_if_fail(y >= 0 && y < _height);

	kanyptr t;
	kuint i;

	switch ((ksize_t)_prop.fmt)
	{
		case QGCF16_RGBA:
			t.u16 = (kushort*)((kbyte*)_data + (y * _pitch) + (x << 1));
			*t.u16 = qgColorWork::TRN_RGBA32_RGBA16(cc.ToUint());
			break;

		case QGCF16_RGB:
			t.u16 = (kushort*)((kbyte*)_data + (y * _pitch) + (x << 1));
			*t.u16 = qgColorWork::TRN_RGBA32_RGB16(cc.ToUint());
			break;

		case QGCF32_RGBA:
			t.u32 = (kuint*)((kbyte*)_data + (y * _pitch) + (x << 2));
			*t.u32 = testblend ? qgBlitWork::PixelInvBlend(*t.u32, cc.ToUint()) : cc.ToUint();
			break;

		case QGCF32_RGB:
			t.u32 = (kuint*)((kbyte*)_data + (y * _pitch) + (x << 2));
			*t.u32 = 0xFF000000 | (testblend ? qgBlitWork::PixelInvBlend(*t.u32, cc.ToUint()) : cc.ToUint());
			break;

		case QGCF32_BGRA:
			i = qgColorWork::TRN_RGBA32_BGRA32(cc.ToUint());
			t.u32 = (kuint*)((kbyte*)_data + (y * _pitch) + (x << 2));
			*t.u32 = testblend ? qgBlitWork::PixelInvBlend(*t.u32, i) : i;
			break;

		case QGCF32_BGR:
			i = qgColorWork::TRN_RGBA32_BGRA32(cc.ToUint());
			t.u32 = (kuint*)((kbyte*)_data + (y * _pitch) + (x << 2));
			*t.u32 = 0xFF000000 | (testblend ? qgBlitWork::PixelInvBlend(*t.u32, i) : i);
			break;
	}
}

//
kuint qgImage::GetPixel(kint x, kint y)
{
	k_return_value_if_fail(x >= 0 && x < _width, 0);
	k_return_value_if_fail(y >= 0 && y < _height, 0);

	switch ((ksize_t)_prop.fmt)
	{
		case QGCF16_RGBA:
			return qgColorWork::TRN_RGBA16_RGBA32(((kushort*)_data)[y * _width + x]);

		case QGCF16_RGB:
			return qgColorWork::TRN_RGB16_RGBA32(((kushort*)_data)[y * _width + x]);

		case QGCF32_RGB:
		case QGCF32_RGBA:
			return ((kuint*)_data)[y * _width + x];

		case QGCF32_BGR:
		case QGCF32_BGRA:
			return qgColorWork::TRN_BGRA32_RGBA32(((kuint*)_data)[y * _width + x]);
	}

	return 0;
}

//
kuint qgImage::GetBox(kint x, kint y, kint fx, kint fy, kint bias)
{
	tpl::ColorValue cc(0, 0, 0, 0);

	for (kint dx = 0; dx < fx; dx++)
	{
		for (kint dy = 0; dy < fy; dy++)
		{
			tpl::ColorValue t(GetPixel(K_MIN(x + dx, _width - 1), K_MIN(y + dy, _height - 1)));
			cc.a += t.a;
			cc.r += t.r;
			cc.g += t.g;
			cc.b += t.b;
		}
	}

	kany a;
	a.f = (float)(fx * fy);
	kint sdiv = ((a.i & 0x7F800000) >> 23) - 127;

	return tpl::ColorValue(
		(kbyte)K_CLAMP((cc.a >> sdiv) + bias, 0, 255),
		(kbyte)K_CLAMP((cc.r >> sdiv) + bias, 0, 255),
		(kbyte)K_CLAMP((cc.g >> sdiv) + bias, 0, 255),
		(kbyte)K_CLAMP((cc.b >> sdiv) + bias, 0, 255));
}

//
void qgImage::CopyTo(qgImage* target)
{
	qgBlitImage::Blit(QG_BLITOP_TEX, target, NULL, tpl::Point(0, 0), this, NULL, tpl::ColorValue(0));
}

//
void qgImage::CopyTo(qgImage* target, const tpl::Point& pos)
{
	qgBlitImage::Blit(QG_BLITOP_TEX, target, NULL, pos, this, NULL, tpl::ColorValue(0));
}

//
void qgImage::CopyToSub(qgImage* target, const tpl::Point& pos, const tpl::Rect& srect)
{
	qgBlitImage::Blit(QG_BLITOP_TEX, target, NULL, pos, this, &srect, tpl::ColorValue(0));
}

//
void qgImage::CopyToSub(qgImage* target, const tpl::Point& pos, const tpl::Rect& srect, const tpl::Rect& clip)
{
	qgBlitImage::Blit(QG_BLITOP_TEX, target, &clip, pos, this, &srect, tpl::ColorValue(0));
}

//
void qgImage::CopyToAlpha(qgImage* target, const tpl::ColorValue& cc, const tpl::Point& pos, const tpl::Rect& srect)
{
	qgBlitImage::Blit(cc.ToUint() == 0xFFFFFFFF ? QG_BLITOP_TEX_ALPHA : QG_BLITOP_TEX_ALPHA_COLOR, target, NULL, pos, this, &srect, cc);
}

//
void qgImage::CopyToAlpha(qgImage* target, const tpl::ColorValue& cc, const tpl::Point& pos, const tpl::Rect& srect, const tpl::Rect& clip)
{
	qgBlitImage::Blit(cc.ToUint() == 0xFFFFFFFF ? QG_BLITOP_TEX_ALPHA : QG_BLITOP_TEX_ALPHA_COLOR, target, &clip, pos, this, &srect, cc);
}

//
void qgImage::CopyToScale(qgImage* target)
{
	k_return_if_fail(target);

	if (target->_width == _width && target->_height == _height)
		CopyTo(target);
	else
		CopyToScaleRaw(target->_data, target->_width, target->_height, target->_prop.fmt, 0);
}

//
void qgImage::CopyToScaleFilter(qgImage* target, kint bias, bool blend /*= false*/)
{
	tpl::Point dim(target->_width, target->_height);
	tpl::Vec2 ss((float)_width / (float)dim.x, (float)_height / (float)dim.y);
	tpl::Point pf((kint)k_ceil(ss.x), (kint)k_ceil(ss.y));

	tpl::Vec2 vs;
	kint x, y;

	for (vs.y = 0.0f, y = 0; y < dim.y; y++)
	{
		for (vs.x = 0.0f, x = 0; x < dim.x; x++)
		{
			kuint c = GetBox((kint)k_floor(vs.x), (kint)k_floor(vs.y), pf.x, pf.y, bias);
			target->SetPixel(x, y, tpl::ColorValue(c), blend);

			vs.x += ss.x;
		}

		vs.y += ss.y;
	}
}

//
void qgImage::CopyToScaleRaw(kpointer data, kint width, kint height, qgColorFormat fmt, kint pitch)
{
	k_return_if_fail(data && width > 0 && height > 0);

	const qbPixelProp& prop = qgAchSupp::ToPixelProp(fmt);

	if (pitch <= 0)
		pitch = width * prop.bpp;

	if (_prop.fmt == fmt && _width == width && _height == height)
	{
		if (_pitch == pitch)
		{
			// 그냥 복사
			memcpy(data, _data, height * pitch);
		}
		else
		{
			// 스캔라인 복사
			kbyte* tgt = (kbyte*)data;
			kbyte* src = (kbyte*)_data;
			kuint bw = width * prop.bpp;
			kuint rs = pitch - bw;

			for (kint y = 0; y < height; y++)
			{
				memcpy(tgt, src, bw);
				memset(tgt + bw, 0, rs);
				tgt += pitch;
				src += _pitch;
			}
		}
	}
	else
	{
		float ssx = (float)_width / (float)width;
		float ssy = (float)_height / (float)height;
		float sy = 0.0f;
		kint yv = 0, syv = 0;
		kint x, y;

		for (y = 0; y < height; y++)
		{
			float sx = 0.0f;

			for (x = 0; x < width; x++)
			{
				qgBlitWork::ColorConvert((kbyte*)_data + syv + (int)sx * _prop.bpp, _prop.fmt, 1, ((kbyte*)data) + yv + (x * prop.bpp), fmt);
				sx += ssx;
			}

			sy += ssy;
			syv = ((kint)sy) * _pitch;
			yv += pitch;
		}
	}
}

//
void qgImage::Fill(const tpl::ColorValue& cc)
{
	kint sbs = _pitch * _height;
	kuint c;

	switch (_prop.fmt)
	{
		case QGCF16_RGBA:
			c = qgColorWork::TRN_RGBA32_RGBA16(cc);
			c |= c << 16;
			break;

		case QGCF16_RGB:
			c = qgColorWork::TRN_RGBA32_RGB16(cc);
			c |= c < 16;
			break;

		case QGCF32_RGBA:
		case QGCF32_RGB:
			c = cc;
			break;

		case QGCF32_BGRA:
		case QGCF32_BGR:
			c = qgColorWork::TRN_RGBA32_BGRA32(cc);
			break;

		default:
			c = 0;
			break;
	}

	//
	kuint* d = (kuint*)_data;
	kint i = sbs >> (2 + 3);

	while (i)
	{
		d[0] = cc;
		d[1] = cc;
		d[2] = cc;
		d[3] = cc;
		d[4] = cc;
		d[5] = cc;
		d[6] = cc;
		d[7] = cc;
		d += 8;
		i--;
	}

	i = (sbs >> 2) & 7;

	while (i)
	{
		*d = cc;
		d++;
		i--;
	}
}

//
bool qgImage::SaveRaw(const char* filename)
{
	k_return_value_if_fail(_data && _size > 0, false);

	kFile* f = k_file_new(filename, "w");
	k_return_value_if_fail(f, false);

	k_file_write(f, _data, 0, _size);

	k_file_delete(f);

	return true;
}

